package main

import "testing"

func TestTailCallOptimization(t *testing.T) {
	env := NewGlobalEnvironment()

	// Define a tail-recursive factorial function
	_, err := EvalString("(define (factorial-iter n acc) (if (= n 0) acc (factorial-iter (- n 1) (* n acc))))", env)
	if err != nil {
		t.Errorf("Error defining factorial-iter: %v", err)
		return
	}

	_, err = EvalString("(define (factorial n) (factorial-iter n 1))", env)
	if err != nil {
		t.Errorf("Error defining factorial: %v", err)
		return
	}

	// Test with small values first
	result, err := EvalString("(factorial 5)", env)
	if err != nil {
		t.Errorf("Error calling tail-recursive factorial: %v", err)
		return
	}

	if result.String() != "120" {
		t.Errorf("Expected 120, got %s", result.String())
	}
}

func TestTailCallWithCountdown(t *testing.T) {
	env := NewGlobalEnvironment()

	// Define a simple tail-recursive countdown function
	_, err := EvalString("(define (countdown n) (if (= n 0) 'done (countdown (- n 1))))", env)
	if err != nil {
		t.Errorf("Error defining countdown: %v", err)
		return
	}

	// Test countdown with a value that would cause stack overflow without TCO
	result, err := EvalString("(countdown 1000)", env)
	if err != nil {
		t.Errorf("Error calling countdown: %v", err)
		return
	}

	if result.String() != "done" {
		t.Errorf("Expected 'done', got %s", result.String())
	}
}

func TestNonTailCallStillWorks(t *testing.T) {
	env := NewGlobalEnvironment()

	// Define a non-tail recursive function (traditional factorial)
	_, err := EvalString("(define (factorial-non-tail n) (if (= n 0) 1 (* n (factorial-non-tail (- n 1)))))", env)
	if err != nil {
		t.Errorf("Error defining non-tail factorial: %v", err)
		return
	}

	// Test that non-tail recursion still works correctly
	tests := []struct {
		input    string
		expected string
	}{
		{"(factorial-non-tail 0)", "1"},
		{"(factorial-non-tail 1)", "1"},
		{"(factorial-non-tail 5)", "120"},
		{"(factorial-non-tail 6)", "720"},
	}

	for _, test := range tests {
		result, err := EvalString(test.input, env)
		if err != nil {
			t.Errorf("Error evaluating %s: %v", test.input, err)
			continue
		}

		if result.String() != test.expected {
			t.Errorf("Expected %s, got %s for input %s", test.expected, result.String(), test.input)
		}
	}
}
